await 命令后面的 Promise 对象,运行结果可能是 rejected,如果 await 后面还有语句也不会再执行
所以最好把 await 命令放在 try...catch 代码块中
错误抛出
async 函数内部抛出错误,会导致返回的 Promise 对象变为 reject 状态
抛出的错误对象会被 catch 方法回调函数接收到
Promise 对象的状态变化
async 函数返回的 Promise 对象,必须等到内部所有 await 命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到 return 语句或者 抛出错误
也就是说,只有 async 函数内部的异步操作执行完,才会执行 then 方法指定的回调函数
使用注意点
多个 await
多个 await 命令后面的异步操作,如果不存在继发关系,最好让它们同时触发
let foo = await getFoo();
let bar = await getBar();getFoo 和 getBar 是两个独立的异步操作(即互不依赖),被写成继发关系。这样比较耗时,因为只有 getFoo 完成以后,才会执行 getBar,完全可以让它们同时触发。
// 写法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);
// 写法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;async 函数可以保留运行堆栈
const a = () => {
b().then(() => c());
};上面代码中,函数 a 内部运行了一个异步任务 b()。当 b() 运行的时候,函数 a() 不会中断,而是继续执行。等到 b() 运行结束,可能 a() 早就运行结束了,b() 所在的上下文环境已经消失了。如果 b() 或 c() 报错,错误堆栈将不包括 a()。
现在将这个例子改成 async 函数。
const a = async () => {
await b();
c();
};上面代码中,b() 运行的时候,a() 是暂停执行,上下文环境都保存着。一旦 b() 或 c() 报错,错误堆栈将包括 a()。
顶层使用 await
主要目的是使用 await 解决模块异步加载的问题
下面是顶层 await 的一些使用场景。
// import() 方法加载
const strings = await import(`/i18n/${navigator.language}`);
// 数据库操作
const connection = await dbConnector();
// 依赖回滚
let jQuery;
try {
jQuery = await import('https://cdn-a.com/jQuery');
} catch {
jQuery = await import('https://cdn-b.com/jQuery');
}注意,如果加载多个包含顶层 await 命令的模块,加载命令是同步执行的。
// x.js
console.log("X1");
await new Promise(r => setTimeout(r, 1000));
console.log("X2");
// y.js
console.log("Y");
// z.js
import "./x.js";
import "./y.js";
console.log("Z");上面代码有三个模块,最后的 z.js 加载 x.js 和 y.js,打印结果是 X1、Y、X2、Z。这说明,z.js 并没有等待 x.js 加载完成,再去加载 y.js。
顶层的 await 命令有点像,交出代码的执行权给其他的模块加载,等异步操作完成后,再拿回执行权,继续向下执行。